/**
* @author   Frederic Saunier - www.tekool.net
* @since    2008/08/10
*
* Copyright (C) 2006 Frederic Saunier
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*
*/

package net.tekool.projects.objscomp.commands;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Logger;

import net.tekool.projects.objscomp.Main;
import net.tekool.projects.objscomp.business.ClassDescriptor;

/**
 * Command that searches directories and sub-directories of the main source
 * folder to extract informations on each Javascript file eligible for
 * conversion into a Javascript class and adds it to a *ClassDecriptor* list.
 * 
 * @author Frederic Saunier
 */
public class ListClasses
{
	private static final Logger log = Logger.getLogger(Main.class.getName());
	
	/**
	 * The way Javascript files are identified.
	 */
	private static final String FILES_END_WITH = ".js";
	
	private String sourcePath;
	private File sourceFile;
	private ArrayList<ClassDescriptor> classes;

	/**
	 * Constructor
	 * 
	 * @param classes
	 *            A *ClassDescriptor* list that the command will fill with
	 *            informations extracted from each Javascript file eligible for
	 *            conversion converted into a Javascript class.
	 * @param sourcePath
	 *            The system absolute file path of the source folder containing
	 *            the Javascript files to convert into a Javascript class.
	 */
	public ListClasses( String sourcePath, ArrayList<ClassDescriptor> classes )
    {
	    super();

	    this.classes = classes;
	    this.sourcePath = sourcePath;
    }
	
	/**
	 * Executes the command.
	 */
	public void execute()
	{
		sourceFile = new File(sourcePath);
		readDirectory( sourceFile, new ArrayList<String>(), 0 );
	}


	/**
	 * Searches directories and sub-directories of an initial directory and add
	 * founds *File* items as file or directory whether the type it is.
	 * 
	 * @param dir
	 *            The directory to read.
	 * @param currentPackage
	 *            A list of classpath elements.
	 */
	private void readDirectory( File dir, ArrayList<String> currentPackage, int level )
	{
		//An helper to identify when the directory we read is the root directory.
		Boolean isRoot = level == 0;
		
		if(!dir.isDirectory())
			return;
		
		if(!dir.canRead())
			return;	
		
		// If the current directory name is not valid we ignore it but log it.
		if( !isRoot && !isValidPackageName(currentPackage.get(level-1)) )
		{
			log.warning("Directory : \"" + dir.getAbsolutePath() + "\" is not a valid package name.");
			return;
		}

		for(File file : dir.listFiles())
		{
			//With recursion the ArrayList is reused, it must be cloned.
			ArrayList<String> outCurrentPackage = new ArrayList<String>(currentPackage);
			
			if(file.isDirectory())
			{				
				outCurrentPackage.add(file.getName());
				int outLevel = level+1;
				readDirectory(file, outCurrentPackage, outLevel );
			}
			else
			{
				//Only Javascript files are read.
				if(file.getName().endsWith(FILES_END_WITH))
				{					
					readFile(file, outCurrentPackage);
				}
			}
		}
	}

	/**
	 * Parse a directory name to check if this will create a valid package name.
	 * 
	 * @param packageArray
	 *            The array of packages name to check.
	 * @return A boolean that specifies whether the directory name is valid.
	 */
	private boolean isValidPackageName( String packageName )
    {
		if(!isValidName(packageName))
			return false;

		return true;
    }
	
	/**
	 * Parse a file name to check if this will create a valid class name.
	 * 
	 * @param fileName The directory name to check.
	 * @return A boolean that specifies whether the file name is valid.
	 */
	private boolean isValidClassName( String fileName )
    {
		String className = getClassName(fileName);
		
	    if(!className.matches("[^0-9][0-9a-zA-Z_$]*"))
	    	return false;

	    return true;
    }
	
	/**
	 * Parse a class or file name to check if it is valid.
	 * 
	 * @param name	The directory name to check.
	 * 
	 * @return	A boolean that specifies whether the file name is valid.
	 */
	private boolean isValidName( String name )
    {
	    return name.matches("[a-zA-Z_$][0-9a-zA-Z_$]*");
    }
	
	/**
	 * Reads the content of a Javascript file validate its content and add the
	 * ClassDescriptor to the result of the command.
	 * 
	 * @param file
	 */
	private void readFile( File file, ArrayList<String> currentPackage )
	{
		if(!file.canRead())
			throw (new Error(file.getPath() + " is not readable"));
		
		// If the file name is not valid we ignore it but log it.
		String fileName = file.getName();
		if(!isValidClassName(fileName))
		{
			log.warning("File name : \"" + fileName + "\" is not a valid class name.");
			return;
		}

		FileReader reader;
		StringBuilder strBuild = new StringBuilder();
		try
		{
			reader = new FileReader(file);

			// TODO We must remove any BOM
			// System.out.println(reader.getEncoding());

			char[] buffer = new char[(int) file.length()];
			while(reader.read(buffer) != -1)
				;
			strBuild.append(buffer);

			reader.close();
		}
		catch(FileNotFoundException e)
		{
			throw (new Error(file.getPath() + " not found"));
		}
		catch(IOException e)
		{
			throw (new Error("Error reading " + file.getPath()));
		}

		ClassDescriptor classDescriptor = new ClassDescriptor
		(
				file,
				strBuild.toString(),
				new ArrayList<String>(currentPackage),
				getClassName(file.getName())
		);

		classes.add(classDescriptor);
	}

	/**
	 * Extracts the class name from a Javascript file name.
	 */
	public static String getClassName( String fileName )
	{
		return fileName.substring(0, fileName.length() - 3);
	}
}
